package com.dman.multi.core;

import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.RandomAccessFile;
import java.io.Serializable;
import java.io.SyncFailedException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;


public class FileStream implements Serializable{
	
	/**
	 * The serial version of the class
	 */
	private static final long serialVersionUID = 1L;
	
	/**
	 * The mode for opening the random access file.
	 */
	public static final String FILE_MODE = "rw";
	
	private String fullFileName = null;
	private long fileSize = 0;
	private String fileName = null;

	private transient RandomAccessFile raf = null;
	
	/**
	 * This is a utility method which tries to compute the file name from the
	 * passed URL.
	 * 
	 * @param url
	 * @return fileName
	 */
	public static String getFileName(URL url){
		String path = url.getPath();
		int index = path.lastIndexOf('/') + 1;
		String fileName = path.substring(index);
		return fileName;
	}
	
	/**
	 * This constructor uses the getFileName utility to pass on the directory and 
	 * file name to the actual constructor.
	 * 
	 * @param dir
	 * @param url
	 * @throws IOException
	 */
	public FileStream(String dir, URL url) throws IOException{
		this(dir, getFileName(url));
	}
	
	/**
	 * Creates a FileStream object which corresponds to the specified directory and
	 * file name. This method ensures that existing files are not overwritten, and
	 * correspondingly renames the download files.
	 * 
	 * @param dir
	 * @param fname
	 * @throws IOException
	 */
	public FileStream(String dir, String fname)	throws IOException{
		
		// check file name for extension and find the same
		int index = fname.lastIndexOf('.');
		String fileName = fname;
		String fileExtn = "";
		if(index > 0){
			fileName = fname.substring(0, index);
			fileExtn = fname.substring(index + 1);
		}
		
		// if this was not a persisted instance
		File diskFile = null;
		if(fullFileName == null){
			// check if the file already exists, if so then create new files with suffix -1, -2 etc
			fullFileName = dir + File.separator + fileName + ((fileExtn.isEmpty())?"":('.' + fileExtn));
			this.fileName = fileName + ((fileExtn.isEmpty())?"":('.' + fileExtn));
			for(int i = 1;; ++i){
				diskFile = new File(fullFileName);
				if(!diskFile.exists()) break;
				fullFileName = dir + File.separator + fileName + '-' + i + ((fileExtn.isEmpty())?"":('.' + fileExtn));
				this.fileName = fileName + '-' + i + ((fileExtn.isEmpty())?"":('.' + fileExtn));
			}
		}
	}
	
	public void createFile() throws IOException{
		raf = new RandomAccessFile(fullFileName, FILE_MODE);
	}
	
	/**
	 * This method reserves the space for the download. It reserves the number of 
	 * bytes specified by the file size parameter.
	 * 
	 * @param file size
	 * @throws IOException
	 */
	public void reserveSpace(long size) throws IOException{
		if(size < 0) 
			size = 0;
		raf.setLength(size);
		fileSize = size;
	}
	
	private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
		in.defaultReadObject();
		createFile();
		if(raf.length() != fileSize)
			throw new IOException("Cannot resume download. File on disk has been modified");
	}
	
	/**
	 * This method writes the data specified by the byte buffer parameter. The offset
	 * for writing the data is computed with the currPos field in Section object. It
	 * returns the number of bytes written into the file.
	 * 
	 * @param Section
	 * @param ByteBuffer
	 * @return number of bytes written
	 * @throws IOException
	 */
	public long writeSection(Section sec, ByteBuffer bb) throws IOException{
		
		FileChannel fileCh = raf.getChannel();
		long currPos = sec.getCurrPos();
		long bytesWritten = 0;
		
		fileCh.position(currPos);
		bytesWritten = fileCh.write(bb);
		currPos += bytesWritten;
		sec.setCurrPos(currPos);
		
		return bytesWritten;
	}
	
	/**
	 * This method ensures that the data written to the File are flushed to disk.
	 * 
	 * @throws SyncFailedException
	 * @throws IOException
	 */
	public void flush() throws SyncFailedException, IOException{
		raf.getFD().sync();
	}
	
	/**
	 * This method  closes the file stream underneath, causing the buffers to be
	 * flushed to disk. This is called just before the JVM would exit.
	 * 
	 * @throws IOException
	 */
	public void close() throws IOException {
		raf.close();
	}
	
	public String getFileName() {
		return fileName;
	}
}
